﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace Aleator.Core.Techniques
{
    /// <summary>
    /// Bouncing singleton
    /// </summary>
    public sealed class Freestyle : Technique
    {
        #region Members

        private static readonly Freestyle m_instance = new Freestyle();

        #endregion

        #region Constructor

        private Freestyle() { }

        #endregion

        #region Public Methods

        /// <summary>
        /// BounceGenerator instance
        /// </summary>
        public new static Freestyle Instance
        {
            get
            {
                return m_instance;
            }
        }

        /// <summary>
        /// Fills the drum phrase with notes
        /// </summary>
        /// <param name="p">Phrase we are filling</param>
        public override void Generate(ConcretePhrase p)
        {
            if (p.GetType() != typeof(DrumPhrase) && p.GetType() != typeof(BassPhrase))
                throw new Exception("Bouncing technique only implemented for drums and bass");

            DrumPhrase dp = (DrumPhrase)p;
            dp.TechniqueType = TechniqueType.Bounce;

            //Calculate totals
            SetTotals(dp);

            //Figure out remainders for all lists
            if (dp.KickNotes.Length > dp.Progression.TotalMeasures)
                dp.KickMod = dp.KickNotes.Length % dp.Progression.Numerator;

            if (dp.SnareNotes.Length > dp.Progression.TotalMeasures)
                dp.SnareMod = dp.SnareNotes.Length % dp.Progression.Numerator;

            if (dp.HiHatNotes.Length > dp.Progression.TotalMeasures)
                dp.HighHatMod = dp.HiHatNotes.Length % dp.Progression.Numerator;

            BuildKick(dp);
            BuildSnare(dp);
            BuildHiHat(dp);
        }
        #endregion

        #region Private Methods

        private static void BuildKick(DrumPhrase dp)
        {
            ConcreteNote tmpNote;

            dp.KickResolution = 2;

            //Determine how many kicks are in each measure without the remainder
            int kicksInMeasure = dp.KickNotes.Length / dp.Progression.TotalMeasures;

            //Set the highest possible location given the meter and resolution
            int playablenotesinmeasure = dp.Progression.Numerator * dp.KickResolution;

            //inner counter will be re-used to add remaining kicks into the phrase below
            int j = 0;
            m_location = 0;

            //Set the top of the range equal the maximum number of quantized notes
            m_continuous.Beta = (dp.Progression.Numerator * dp.KickResolution) - 1;
            m_continuous.Alpha = 0;

            m_rayleigh.Sigma = 4.0;

            //Fill out the first measure of the phrase
            while (j < kicksInMeasure)
            {
                m_instance.GetBeat(dp, DrumPhrase.Type.Kick);

                //If the number returned by the generator is within the desired range and has not already been selected,
                //We can add it it to the note array
                if (!dp.KickNotes.Where(k => k != null && k.Location == m_location && k.Measure == 0).Any())
                {
                    //Create the new beat and add it to the kick array, passing in the beat, measure (i) and note numbers
                    dp.KickNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, 0, DrumPhrase.KickNum, DrumPhrase.KickOnMessageNumber, DrumPhrase.KickOffMessageNumber, dp);
                    dp.Notes.Add(dp.KickNotes[j]);
                    j++;
                }
            }

            //Fill out the rest
            for (int i = 1; i < dp.Progression.TotalMeasures; i++)
            {
                if (j > dp.Progression.TotalMeasures * kicksInMeasure)
                    break;

                for (int k = 0; k < kicksInMeasure; k++)
                {
                    tmpNote = dp.KickNotes[j % kicksInMeasure];
                    dp.KickNotes[j] = (ConcreteNote)m_notefactory.GetNote(tmpNote.Location, i, DrumPhrase.KickNum, DrumPhrase.KickOnMessageNumber, DrumPhrase.KickOffMessageNumber, dp);
                    dp.Notes.Add(dp.KickNotes[j]);
                    j++;
                }
            }

            //Get any beat that isn't in the kick collection yet
            List<int> beats = new List<int>();
            List<ConcreteNote> fullnotes = dp.KickNotes.Where(k => k != null).ToList();

            try
            {
                for (int i = 0; i < dp.Progression.Numerator * dp.KickResolution; i++)
                {
                    if (fullnotes.Where(n => n.Location == i).Count() == 0)
                        beats.Add(i);
                }
            }
            catch (NullReferenceException nre)
            {
                Debug.WriteLine("Error message: " + nre.Message);
            }

            if (beats.Any())
            {
                m_location = beats[Session.RndGenerator.Next(beats.Count)];

                //Add the remaining notes to the array to fill it up using the same process
                while (j < dp.KickNotes.Length)
                {
                    m_measure = Session.RndGenerator.Next(dp.Progression.TotalMeasures);

                    if (dp.KickNotes.Where(k => k != null && k.Location == m_location && k.Measure == m_measure).Count() == 0)
                    {
                        dp.KickNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, m_measure, DrumPhrase.KickNum, DrumPhrase.KickOnMessageNumber, DrumPhrase.KickOffMessageNumber, dp);
                        dp.Notes.Add(dp.KickNotes[j]);
                        j++;
                    }
                }
            }
        }

        private static void BuildSnare(DrumPhrase dp)
        {
            ConcreteNote tmpNote;

            //Quantize the snare to 16ths (quarters / 4)
            dp.SnareResolution = 4;

            //Determine how many kicks are in each measure without the remainder
            int snaresInMeasure = dp.SnareNotes.Length / dp.Progression.TotalMeasures;

            //Set the highest possible location given the meter and resolution
            int playablenotesinmeasure = dp.Progression.Numerator * dp.SnareResolution;

            m_continuous.Beta = dp.Progression.Numerator * dp.SnareResolution;
            m_rayleigh.Sigma = (double)dp.SnareResolution;

            //inner counter will be re-used to add remaining kicks into the phrase below
            int j = 0;
            m_location = 0;

            //Fill out the first measure of the phrase
            while (j < snaresInMeasure)
            {
                m_instance.GetBeat(dp, DrumPhrase.Type.Snare);

                //If the number returned by the generator is within the desired range and has not already been selected,
                //We can add it it to the note array
                if (!dp.SnareNotes.Where(k => k != null && k.Location == m_location).Any())
                {
                    //Create the new beat and add it to the kick array, passing in the beat, measure (i) and note numbers
                    dp.SnareNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, 0, DrumPhrase.SnareNum, DrumPhrase.SnareOnMessageNumber, DrumPhrase.SnareOffMessageNumber, dp);
                    dp.Notes.Add(dp.SnareNotes[j]);
                    j++;
                }
            }

            //Fill out the rest
            for (int i = 1; i < dp.Progression.TotalMeasures; i++)
            {
                if (j > dp.Progression.TotalMeasures * snaresInMeasure)
                    break;

                for (int k = 0; k < snaresInMeasure; k++)
                {
                    tmpNote = dp.SnareNotes[j % snaresInMeasure];
                    dp.SnareNotes[j] = (ConcreteNote)m_notefactory.GetNote(tmpNote.Location, i, DrumPhrase.SnareNum, DrumPhrase.SnareOnMessageNumber, DrumPhrase.SnareOffMessageNumber, dp);
                    dp.Notes.Add(dp.SnareNotes[j]);
                    j++;
                }
            }

            //Get any beat that isn't in the kick collection yet
            List<int> beats = new List<int>();
            List<ConcreteNote> fullnotes = dp.SnareNotes.Where(k => k != null).ToList();

            try
            {
                for (int i = 0; i < dp.Progression.Numerator * dp.SnareResolution; i++)
                {
                    if (fullnotes.Where(n => n.Location == i).Count() == 0)
                        beats.Add(i);
                }
            }
            catch (NullReferenceException nre)
            {
                Debug.WriteLine("Error message: " + nre.Message);
            }

            if (beats.Any())
            {
                m_location = beats[Session.RndGenerator.Next(beats.Count)];

                //Add the remaining notes to the array to fill it up using the same process
                while (j < dp.SnareNotes.Length)
                {
                    m_measure = Session.RndGenerator.Next(dp.Progression.TotalMeasures);

                    if (dp.SnareNotes.Where(k => k != null && k.Location == m_location && k.Measure == m_measure).Count() == 0)
                    {
                        dp.SnareNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, m_measure, DrumPhrase.SnareNum, DrumPhrase.SnareOnMessageNumber, DrumPhrase.SnareOffMessageNumber, dp);
                        dp.Notes.Add(dp.SnareNotes[j]);
                        j++;
                    }
                }
            }
        }

        private static void BuildHiHat(DrumPhrase dp)
        {
            ConcreteNote tmpNote;
            dp.HiHatResolution = 4;

            //Determine how many kicks are in each measure without the remainder
            int highHatsInMeasure = dp.HiHatNotes.Length / dp.Progression.TotalMeasures;

            //Set the top of the range equal the maximum number of quantized notes
            m_continuous.Beta = dp.Progression.Numerator * dp.HiHatResolution;

            //inner counter will be re-used to add remaining kicks into the phrase below
            int j = 0;
            m_location = 0;

            //Fill out the first measure of the phrase
            while (j < highHatsInMeasure)
            {
                m_location = Math.Min((int)Math.Round(m_continuous.NextDouble()), (dp.Progression.Numerator * dp.HiHatResolution) - 1);

                //If the number returned by the generator is within the desired range and has not already been selected,
                //We can add it it to the note array
                if (!dp.HiHatNotes.Where(k => k != null && k.Location == m_location).Any())
                {
                    //Create the new beat and add it to the kick array, passing in the beat, measure (i) and note numbers
                    dp.HiHatNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, 0, DrumPhrase.HiHatNum, DrumPhrase.HiHatOnMessageNumber, DrumPhrase.HiHatOffMessageNumber, dp);
                    dp.Notes.Add(dp.HiHatNotes[j]);
                    j++;
                }
            }

            //Fill out the rest
            for (int i = 1; i < dp.Progression.TotalMeasures; i++)
            {
                if (j > dp.Progression.TotalMeasures * highHatsInMeasure)
                    break;

                for (int k = 0; k < highHatsInMeasure; k++)
                {
                    tmpNote = dp.HiHatNotes[j % highHatsInMeasure];
                    dp.HiHatNotes[j] = (ConcreteNote)m_notefactory.GetNote(tmpNote.Location, i, DrumPhrase.HiHatNum, DrumPhrase.HiHatOnMessageNumber, DrumPhrase.HiHatOffMessageNumber, dp);
                    dp.Notes.Add(dp.HiHatNotes[j]);
                    j++;
                }
            }

            //Get any beat that isn't in the kick collection yet
            List<int> beats = new List<int>();
            List<ConcreteNote> fullnotes = dp.HiHatNotes.Where(k => k != null).ToList();

            try
            {
                for (int i = 0; i < dp.Progression.Numerator * dp.HiHatResolution; i++)
                {
                    if (fullnotes.Where(n => n.Location == i).Count() == 0)
                        beats.Add(i);
                }
            }
            catch (NullReferenceException nre)
            {
                Debug.WriteLine("Error message: " + nre.Message);
            }

            m_measure = 0;
            m_location = beats[Session.RndGenerator.Next(beats.Count)];

            //Add the remaining notes to the array to fill it up using the same process
            while (j < dp.HiHatNotes.Length)
            {
                m_measure = Session.RndGenerator.Next(dp.Progression.TotalMeasures);

                if (dp.HiHatNotes.Where(k => k != null && k.Location == m_location && k.Measure == m_measure).Count() == 0)
                {
                    dp.HiHatNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, m_measure, DrumPhrase.HiHatNum, DrumPhrase.HiHatOnMessageNumber, DrumPhrase.HiHatOffMessageNumber, dp);
                    dp.Notes.Add(dp.HiHatNotes[j]);
                    j++;
                }
            }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Set note totals
        /// </summary>
        /// <param name="p">Phrase we are filling</param>
        protected override void SetTotals(Phrase p)
        {
            DrumPhrase dp = (DrumPhrase)p;

            int kicks = 0;
            int snares = 0;
            int highhats = 0;

            if (dp.PhraseRhythm.RhythmType == 2)
            {
                kicks = m_instance.GetTotal(dp.Progression.TotalMeasures, dp.Progression.TotalBeats);
                snares = m_instance.GetTotal(dp.Progression.TotalMeasures, dp.Progression.TotalBeats);
                highhats = m_instance.GetTotal(dp.Progression.TotalMeasures, dp.Progression.TotalBeats * 2);
            }
            else
                throw new NotImplementedException("Bounce technique only implemented for bouncing rhythm types.");

            dp.KickNotes = new ConcreteNote[kicks];
            dp.SnareNotes = new ConcreteNote[snares];
            dp.HiHatNotes = new ConcreteNote[highhats];

            dp.Notes = new List<ConcreteNote>();
        }

        /// <summary>
        /// Get note totas for the phrase
        /// </summary>
        /// <param name="totalmeasures">Total measures for the phrase</param>
        /// <param name="mu">Mu value for distribution object</param>
        /// <returns></returns>
        //protected override int GetTotal(int totalmeasures, double mu)
        //{
        //    //Use normal distribution to calculate drum note totals
        //    m_normal.Mu = mu;
        //    m_normal.Sigma = 2.0;

        //    double next = 0.0;
        //    int intnext = 0;

        //    while (Convert.ToInt32(next) <= 0)
        //        next = m_normal.NextDouble();

        //    intnext = (int)next;

        //    //This ensures that there is at least one of each in every measure. Will probably need to change this later.
        //    if (intnext < totalmeasures)
        //        return totalmeasures;
        //    else
        //        return intnext;
        //}

        #endregion
    }
}